/*
Fx是Effect的简写，顾名思义，是用于产生效果的基类
事实上，Fx的名气更多是在作为Prototype效果插件时被称作Moo.fx的轻量级实现
其实就是用最少的代码，做最好的事
*/

var Fx = new Class({


 //链式执行，事件和可选参数的接口实现

 Implements: [Chain, Events, Options],

 

 options: {

  /*

  onStart: $empty, //特效开始时的事件

  onCancel: $empty, //特效取消时的事件

  onComplete: $empty, //特效完成时的事件

  */  

  fps: 50, //frame per second，每秒的帧数，用于计时器的时间间隔换算

  unit: false, //单位，派生类使用

  duration: 500,
 //周期，特效完成所花的时间
  link: 'ignore', //当新旧两次特效转换冲突时的处理

  //转换，其实就是目标数值的变化速度函数，可以根据曲线看出速度的变化规律
  transition: function(p){

   return -(Math.cos(Math.PI * p) - 1) / 2;

  }

 },


 //构造函数

 initialize: function(options){

  this.subject = this.subject || this;

  this.setOptions(options);
  //先判断是否使用易记的命名周期，否则再取整

  this.options.duration = Fx.Durations[this.options.duration] || this.options.duration.toInt();
  //同下兼容的写法，旧版本使用的是wait参数

  var wait = this.options.wait;
  //使用绝对等于，避免不传wait参数时的意外

  if (wait === false) this.options.link = 'cancel';

 },


 //每一步执行的操作

 step: function(){
  //取当前时间

  var time = $time();
  //如果没到结束时间

  if (time < this.time + this.options.duration){
   //计算出转换效果中的变量值

   var delta = this.options.transition((time - this.time) / this.options.duration);
   //根据变量设置目标值

   this.set(this.compute(this.from, this.to, delta));
  //执行结束

  } else {
   //此时变量值为定值1

   this.set(this.compute(this.from, this.to, 1));

   this.complete();

  }

 },


 //设置目标值，留作派生类实现

 set: function(now){

  return now;

 },


  //根据初始值，结束值和变量求目标值
 compute: function(from, to, delta){

  //使用因为有很多效果使用相同的算法，所以使用静态方法

  return Fx.compute(from, to, delta);

 },

 

 //检查当前特效运行状态

 check: function(){
  //如果特效没有运行

  if (!this.timer) return true;

  switch (this.options.link){
   //如果wait为false或link为cancel，不等待正在运行的特效，直接取消并重新开始

   case 'cancel': this.cancel(); return true;
   //如果link为cancel，等待当前特效运行结束后再继续运行新特效

   case 'chain': this.chain(this.start.bind(this, arguments)); return false;
   //其它取值是等同于ignore，直接忽略新特效，继续完成当前特效

  }

  return false;

 },


 //特效开始，从from变换到to

 start: function(from, to){
  //检查当前特效运行状态，决定是否运行新特效

  if (!this.check(from, to)) return this;

  //因为在其它地方多次用到，所以需要转为属性

  this.from = from;

  this.to = to;

  this.time = 0;
  //开始计时

  this.startTimer();
  //发送onStart事件通知

  this.onStart();

  return this;

 },


 //完成特效

 complete: function(){
  //停止计时器，并触发onComplete事件

  if (this.stopTimer()) this.onComplete();

  return this;

 },


 //取消正在执行的特效
 cancel: function(){

  //停止计时器，并触发onCancel事件

  if (this.stopTimer()) this.onCancel();

  return this;

 },


 //发送onStart事件通知

 onStart: function(){
  //注意传给事件监听的参数为this.subject

  this.fireEvent('onStart', this.subject);

 },

 

 //发送onComplete事件通知

 onComplete: function(){

  //注意传给事件监听的参数为this.subject

  this.fireEvent('onComplete', this.subject);
  //链式执行

  if (!this.callChain()) this.fireEvent('onChainComplete', this.subject);

 },

 

 //发送onCancel事件通知

 onCancel: function(){

  //触发onCancel事件并取消链式执行，注意传给事件监听的参数为this.subject

  this.fireEvent('onCancel', this.subject).clearChain();

 },


 //暂停特效

 pause: function(){
  //只需要停止计时器

  this.stopTimer();

  return this;

 },


 //特效暂停后的恢复

 resume: function(){
  //重新开始时间，因为this.time已经有进度记录，所以可以直接开始计时就能恢复到暂停前状态
  this.startTimer();

  return this;

 },


 //停止计时器

 stopTimer: function(){
  //如果没有运行或已停止，忽略

  if (!this.timer) return false;
  //更新this.time，以用于resume的特效恢复

  this.time = $time() - this.time;
  //停止计时器

  this.timer = $clear(this.timer);

  return true;

 },


 //开始计时

 startTimer: function(){
  //如果已开始计时，忽略

  if (this.timer) return false;
  //时间差

  this.time = $time() - this.time;
  //开始计时器，注意要将fps换算，计算出每次执行的时间间隔

  this.timer = this.step.periodical(Math.round(1000 / this.options.fps), this);

  return true;

 }

 

});


//目标值的计算算法，不同的算法实现不同的转换效果

Fx.compute = function(from, to, delta){

 return (to - from) * delta + from;

};


//周期的几个命名点
Fx.Durations = {'short': 250, 'normal': 500, 'long': 1000};